<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <style>
    * {
      margin: 0;
      padding: 0;
    }
    .box-a {
      width: 200px;
      height: 200px;
      border: 1px solid #ccc;
      height: 2000px;
      /* margin-top: 800px; */
    }
    .box-b {
      width: 200px;
      height: 200px;
      border: 1px solid blue;
    }
    .box {
      width: 200px;
      height: 200px;
      border: 1px solid blue;
      position: relative;
    }
    .box-c {
      width: 150px;
      height: 150px;
      border: 1px solid orange;
    }
    .box-d {
      margin: 100px auto;
      width: 200px;
      height: 200px;
      border: 1px solid red;
    }
    .tooltip {
      padding: 10px 15px;
      border: 1px solid #ccc;
      border-radius: 4px;
      position: absolute;
      top: 50px;
    }
    .box1 {
      height: 100%;
      width: 100%;
      position: absolute;
      left: 0;
      top: 10px;
    }
  </style>
  <body>
    <div class="box-a"></div>
    <div class="box">
      <div class="tooltip">tooltip</div>
    </div>
  </body>
  <script>
    const Default = {
      placement: "top",
      //
      modifiers: ["prevenetOverflow", "flip", "applyStyle"],
      preventOverflowOrder: ["left", "right", "top", "bottom"],
    };
    let boxD = document.querySelector(".box-a");
    let toolTip = document.querySelector(".tooltip");
    // function isFixed(element) {
    //   if (element === document.body) {
    //     return false;
    //   }
    // }
    // isFixed(boxD.parentNode);
    class Popper {
      constructor(refrence, popper, options) {
        this._refrence = refrence;
        this._popper = popper;
        this._options = Object.assign({}, options, Default);
        this._options.modifiers = this._options.modifiers.map(
          function (modifier) {
            if (modifier === "applyStyle") {
              this._popper.setAttribute("x-placement", this._options.placement);
            }
            return this.modifiers[modifier] || modifier;
          }.bind(this)
        );
        this._origanlPlacement = this._options.placement;
        console.log(this._options);
        setStyle(this._popper, { position: "absolute" });
        this.update();
      }
      update() {
        let data = { instance: this };
        data.placement = this._options.placement.split("-")[0];
        data.offsets = this._getOffset(
          this._refrence,
          this._popper,
          data.placement
        );
        data.boundaries = this._getBoundariesRect(data);
        this.runModifires(data);
      }
      _getOffset(refrence, popper, placement) {
        // 拿到带有position属性的 tooltip的父级的dom节点
        let refrence_offsets = this._customBoundingRect(
          refrence,
          getPoperOffsetParent(popper),
          placement
        );
        let popperRect = getOuterSize(this._popper);
        let popper_offset = {};
        if (["left", "right"].indexOf(placement) !== -1) {
          popper_offset.top =
            refrence_offsets.top +
            refrence_offsets.height / 2 -
            popperRect.height / 2;
          if (placement === "left") {
            popper_offset.left = popperRect.width - refrence_offsets.left;
          } else {
            popper_offset.left = refrence_offsets.right;
          }
        } else {
          if (["top", "bottom"].indexOf(placement) !== -1) {
            popper_offset.left =
              refrence_offsets.left +
              refrence_offsets.width / 2 -
              popperRect.width / 2;
            if (placement === "top") {
              popper_offset.top = refrence_offsets.top - popperRect.height;
            } else {
              popper_offset.top = refrence_offsets.bottom;
            }
          }
        }
        popper_offset.width = popperRect.width;
        popper_offset.height = popperRect.height;
        return {
          refrence_offsets,
          popper_offset,
        };
      }
      _customBoundingRect(refrence, parentOffsetDom) {
        let refrence_rect = refrence.getBoundingClientRect();
        let parent_rect = parentOffsetDom.getBoundingClientRect();
        // TODO 暂时不考虑fixed的问题
        return {
          width: refrence_rect.width,
          height: refrence_rect.height,
          top: refrence_rect.top - parent_rect.top,
          left: refrence_rect.left - parent_rect.left,
          bottom: refrence_rect.top - parent_rect.top + refrence_rect.height,
          right: refrence_rect.left - parent_rect.left + refrence_rect.width,
        };
      }
      runModifires(data) {
        this._options.modifiers.forEach((fn) => {
          if (Object.prototype.toString.call(fn) === "[object Function]") {
            data = fn.call(this, data);
          }
        });
        return data;
      }
      _getBoundariesRect(data) {
        let offsetParent = getPoperOffsetParent(this._popper);
        let offsetParentRect = offsetParent.getBoundingClientRect();
        return {
          top: 0 - offsetParentRect.top + 5,
          bottom:
            document.documentElement.clientHeight - offsetParentRect.top - 5,
        };
      }
    }
    Popper.prototype.modifiers = {};
    Popper.prototype.modifiers.prevenetOverflow = function (data) {
      // 判断当前元素到refrence上部的距离和当前元素到页面顶部的距离哪个比较大
      let popperOffset = getPopperRect(data.offsets.popper_offset);
      let order = this._options.preventOverflowOrder;
      let boundaries = data.boundaries;
      let check = {
        top: function () {
          let top = popperOffset.top;
          if (popperOffset.top < boundaries.top) {
            top = Math.max(top, boundaries.top);
          }
          return {
            top,
          };
        },
        bottom: function () {
          let top = popperOffset.top;
          if (popperOffset.bottom > boundaries.bottom) {
            console.log(
              popperOffset.bottom,
              boundaries.bottom - popperOffset.height * 1
            );
            top = Math.min(
              popperOffset.bottom,
              boundaries.bottom - popperOffset.height * 1
            );
          }
          return {
            top,
          };
        },
      };
      order.forEach((placement) => {
        Object.assign(
          data.offsets.popper_offset,
          check[placement] && check[placement]()
        );
      });
      return data;
    };
    Popper.prototype.modifiers.flip = function (data) {
      let refrenceOffset = data.offsets.refrence_offsets;
      let placement = data.placement;
      let popperOffset = getPopperRect(data.offsets.popper_offset);
      let placementOpposite = getOppesitePlacement(placement);
      let flipOrder = [placement, placementOpposite];
      if (data.flieped && this._origanlPlacement === data.placement) {
        console.log("end");
        return data;
      }
      flipOrder.forEach((step, index) => {
        console.log(placement, step);
        if (placement !== step || flipOrder.length === index + 1) {
          return;
        }
        placement = data.placement.split("-")[0];
        placementOpposite = getOppesitePlacement(placement);
        a = ["right", "bottom"].indexOf(placement) !== -1;
        console.log(
          placement,
          refrenceOffset[placement],
          popperOffset[placementOpposite]
        );
        if (
          (a &&
            Math.floor(refrenceOffset[placement]) >
              Math.floor(popperOffset[placementOpposite])) ||
          (!a &&
            Math.floor(refrenceOffset[placement]) <
              Math.floor(popperOffset[placement]))
        ) {
          console.log(1, placementOpposite);
          data.placement = flipOrder[index + 1];
          data.flieped = true;
          data.offsets.popper_offset = this._getOffset(
            this._refrence,
            this._popper,
            placementOpposite
          ).popper_offset;
          data = this.runModifires(data, this._options.modifiers);
        }
      });

      return data;
    };
    Popper.prototype.modifiers.applyStyle = function (data) {
      this._popper.style.top = data.offsets.popper_offset.top + "px";
      this._popper.style.left = data.offsets.popper_offset.left + "px";

      return data;
    };
    function getOppesitePlacement(placement) {
      let map = {
        top: "bottom",
        bottom: "top",
      };
      return map[placement];
    }
    function getOuterSize(element) {
      let _display = element.style.display;
      let _visible = element.style.visible;
      element.style.display = "block";
      element.style.visible = "hidden";
      let style = window.getComputedStyle(element);
      let x = parseFloat(style.marginLeft) + parseFloat(style.marginRight);
      let y = parseFloat(style.marginTop) + parseFloat(style.marginBottom);
      let result = {
        width: element.offsetWidth + x,
        height: element.offsetHeight + y,
      };
      element.style.display = _display;
      element.style.visible = _visible;
      return result;
    }
    function getPoperOffsetParent(element) {
      var offsetParent = element.offsetParent;
      return offsetParent === document.body || !offsetParent
        ? document.documentElement
        : offsetParent;
    }
    function addStypeElement(element, key, value) {
      element.style[key] = value;
    }
    function setStyle(element, styles) {
      let unit = "";
      function isNum(value) {
        return value !== "" && isNaN(parseFloat(value)) && isFinite(value);
      }
      Object.keys(styles).map((item) => {
        if (
          ["width", "height", "top", "right", "bottom", "left"].indexOf(
            item
          ) !== -1 &&
          isNum(styles[item])
        ) {
          unit = unit + "px";
        }
        element.style[item] = styles[item] + unit;
      });
    }
    function getPopperRect(popperOffset) {
      popperOffset.bottom = popperOffset.top + popperOffset.height;
      return popperOffset;
    }
    let popper = new Popper(boxD, toolTip);
    document.onscroll = function () {
      popper.update();
    };
  </script>
</html>
